use anyhow::{Context, Result};
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Emitter, Listener, Manager, State};
use tracing::{debug, error, info, warn};

mod connection;
mod state;

use connection::ConnectionManager;
use state::{ConnectionConfig, ConnectionState, EventBroadcaster, RitermState, ServerMessage};


/// Application-wide state for managing active connections
pub struct AppConnections {
    pub connection_manager: Arc<Mutex<Option<ConnectionManager>>>,
    pub chat_sender: Arc<Mutex<Option<shared::ChatSender>>>,
    pub chat_node: Arc<Mutex<Option<shared::ChatNode>>>,
}

impl Default for AppConnections {
    fn default() -> Self {
        Self {
            connection_manager: Arc::new(Mutex::new(None)),
            chat_sender: Arc::new(Mutex::new(None)),
            chat_node: Arc::new(Mutex::new(None)),
        }
    }
}

impl AppConnections {
    pub async fn shutdown(&self) -> Result<()> {
        // Shutdown chat node if it exists
        let node = {
            let mut guard = self.chat_node.lock().unwrap();
            guard.take()
        };
        if let Some(node) = node {
            node.shutdown().await;
            info!("Chat node shutdown complete");
        }

        // Clear connection manager
        *self.connection_manager.lock().unwrap() = None;
        *self.chat_sender.lock().unwrap() = None;

        Ok(())
    }
}

// Tauri commands
#[tauri::command]
async fn connect_to_cli(
    config: ConnectionConfig,
    app: AppHandle,
    state: State<'_, Arc<RitermState>>,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!("Starting connection to CLI with config: {:?}", config);

    // Parse secret key if provided
    let secret_key = if let Some(secret_str) = config.secret_key {
        let bytes = data_encoding::HEXLOWER
            .decode(secret_str.as_bytes())
            .map_err(|e| format!("Failed to parse secret key: {}", e))?;
        if bytes.len() == 32 {
            let mut array = [0u8; 32];
            array.copy_from_slice(&bytes);
            Some(shared::SecretKey::from_bytes(&array))
        } else {
            return Err("Secret key must be exactly 32 bytes".to_string());
        }
    } else {
        None
    };

    // Create chat node
    let node = shared::ChatNode::spawn(secret_key)
        .await
        .map_err(|e| format!("Failed to spawn chat node: {}", e))?;

    let node_id = node.node_id();
    info!("Created chat node with ID: {}", node_id);

    // Parse ticket
    let ticket = shared::ChatTicket::deserialize(&config.ticket)
        .map_err(|e| format!("Failed to parse ticket: {}", e))?;

    // Join the channel
    let nickname = config
        .nickname
        .clone()
        .unwrap_or_else(|| "riterm-client".to_string());
    let (chat_sender, mut receiver) = node
        .join(&ticket, nickname.clone())
        .await
        .map_err(|e| format!("Failed to join channel: {}", e))?;

    // Store connections
    {
        let mut connections_guard = connections.chat_node.lock().unwrap();
        *connections_guard = Some(node);
    }
    {
        let mut connections_guard = connections.chat_sender.lock().unwrap();
        *connections_guard = Some(chat_sender.clone());
    }

    // Update state
    state
        .update_connection_state(|state| {
            state.node_id = Some(node_id.to_string());
            state.ticket = Some(config.ticket.clone());
            state.is_connected = true;
        })
        .await
        .map_err(|e| e.to_string())?;

    // Create event broadcaster
    let event_broadcaster = EventBroadcaster::new();

    // Store event broadcaster for later use
    state.set_event_broadcaster(event_broadcaster.clone()).await;

    // Start event broadcasting BEFORE broadcasting
    let app_clone = app.clone();
    let mut event_receiver = event_broadcaster.subscribe().await;

    tokio::spawn(async move {
        while let Some(message) = event_receiver.recv().await {
            let event_name = match &message {
                ServerMessage::Connected { .. } => "riterm://connected",
                ServerMessage::Disconnected => "riterm://disconnected",
                ServerMessage::TerminalCreated { .. } => "riterm://terminal-created",
                ServerMessage::TerminalOutput { session_id, .. } => {
                    // Emit session-specific events for terminals
                    let _ = app_clone.emit(
                        &format!("riterm://terminal-output-{}", session_id),
                        &message,
                    );
                    "riterm://terminal-output"
                }
                ServerMessage::TerminalStatus { .. } => "riterm://terminal-status",
                ServerMessage::TerminalStateChanged { .. } => "riterm://terminal-state-changed",
                ServerMessage::NodeJoined { .. } => "riterm://node-joined",
                ServerMessage::NodeLeft { .. } => "riterm://node-left",
                ServerMessage::WebShareStarted { .. } => "riterm://web-share-started",
                ServerMessage::WebShareStopped { .. } => "riterm://web-share-stopped",
                ServerMessage::Error { .. } => "riterm://error",
            };

            if let Err(e) = app_clone.emit(event_name, &message) {
                error!("Failed to emit event {}: {}", event_name, e);
            }
        }
    });

    // Small delay to ensure event listener is ready
    tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;

    // Broadcast connection success AFTER listener is ready
    event_broadcaster
        .broadcast(ServerMessage::Connected {
            node_id: node_id.to_string(),
            ticket: config.ticket.clone(),
        })
        .await;

    // Start handling gossip events
    let app_clone = app.clone();

    tokio::spawn(async move {
        use n0_future::StreamExt;
        while let Some(event_result) = receiver.next().await {
            match event_result {
                Ok(event) => {
                    // Get state from app handle for each event
                    if let Some(state_manager) = app_clone.try_state::<Arc<RitermState>>() {
                        if let Err(e) =
                            handle_gossip_event(event, &app_clone, &state_manager, node_id).await
                        {
                            error!("Error handling gossip event: {}", e);
                        }
                    } else {
                        error!("Could not access RitermState");
                    }
                }
                Err(e) => {
                    error!("Error receiving gossip event: {}", e);
                    break;
                }
            }
        }

        // Connection lost
        warn!("Gossip event stream ended, connection lost");

        // Update connection state and broadcast disconnection
        if let Some(state_manager) = app_clone.try_state::<Arc<RitermState>>() {
            let _ = state_manager
                .update_connection_state(|state| {
                    state.is_connected = false;
                })
                .await;

            // Broadcast disconnection through event broadcaster
            if let Some(broadcaster) = state_manager.get_event_broadcaster().await {
                broadcaster.broadcast(ServerMessage::Disconnected).await;
            } else {
                // Fallback: direct Tauri event
                if let Err(e) = app_clone.emit("riterm://disconnected", &()) {
                    error!("Failed to emit disconnection event: {}", e);
                }
            }
        }
    });

    info!("Successfully connected to remote CLI");
    Ok(())
}

#[tauri::command]
async fn disconnect_from_cli(
    app: AppHandle,
    state: State<'_, Arc<RitermState>>,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!("Disconnecting from remote CLI");

    // Shutdown connections
    connections
        .shutdown()
        .await
        .context("Failed to shutdown connections")
        .map_err(|e| e.to_string())?;

    // Update state
    state
        .update_connection_state(|state| {
            state.is_connected = false;
            state.connected_terminals.clear();
            state.active_proxies.clear();
            state.peers.clear();
        })
        .await
        .map_err(|e| e.to_string())?;

    // Broadcast disconnection through event broadcaster
    if let Some(broadcaster) = state.get_event_broadcaster().await {
        broadcaster.broadcast(ServerMessage::Disconnected).await;
    } else {
        // Fallback: direct Tauri event
        if let Err(e) = app.emit("riterm://disconnected", &()) {
            error!("Failed to emit disconnection event: {}", e);
        }
    }

    info!("Disconnected from remote CLI");
    Ok(())
}

#[tauri::command]
async fn create_terminal(
    sessionId: String,
    shellPath: Option<String>,
    name: Option<String>,
    app: AppHandle,
    _state: State<'_, Arc<RitermState>>,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!("create_terminal called with sessionId: {:?}, shellPath: {:?}, name: {:?}", sessionId, shellPath, name);
    info!("Creating terminal: {} (name: {:?})", sessionId, name);

    let chat_sender = connections
        .chat_sender
        .lock()
        .unwrap()
        .clone()
        .ok_or_else(|| "Not connected to CLI".to_string())?;

    chat_sender
        .create_terminal(sessionId.clone(), shellPath, name)
        .await
        .context("Failed to create terminal")
        .map_err(|e| e.to_string())?;

    // Broadcast terminal creation
    if let Err(e) = app.emit(
        "riterm://terminal-created",
        &ServerMessage::TerminalCreated {
            session_id: sessionId,
        },
    ) {
        error!("Failed to emit terminal-created event: {}", e);
    }

    Ok(())
}

#[tauri::command]
async fn send_terminal_input(
    sessionId: String,
    data: Vec<u8>,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!(
        "send_terminal_input called with sessionId: {:?}, data.len: {}",
        sessionId,
        data.len()
    );
    debug!(
        "Sending terminal input to {}: {} bytes",
        sessionId,
        data.len()
    );

    let chat_sender = connections
        .chat_sender
        .lock()
        .unwrap()
        .clone()
        .ok_or_else(|| "Not connected to CLI".to_string())?;

    chat_sender
        .send_terminal_input(sessionId, data)
        .await
        .context("Failed to send terminal input")
        .map_err(|e| e.to_string())?;

    Ok(())
}

#[tauri::command]
async fn resize_terminal(
    sessionId: String,
    rows: u32,
    cols: u32,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    debug!("Resizing terminal {} to {}x{}", sessionId, rows, cols);

    let chat_sender = connections
        .chat_sender
        .lock()
        .unwrap()
        .clone()
        .ok_or_else(|| "Not connected to CLI".to_string())?;

    chat_sender
        .resize_terminal(sessionId, rows, cols)
        .await
        .context("Failed to resize terminal")
        .map_err(|e| e.to_string())?;

    Ok(())
}

#[tauri::command]
async fn close_terminal(
    sessionId: String,
    app: AppHandle,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!("Closing terminal: {}", sessionId);

    let chat_sender = connections
        .chat_sender
        .lock()
        .unwrap()
        .clone()
        .ok_or_else(|| "Not connected to CLI".to_string())?;

    chat_sender
        .close_terminal(sessionId.clone())
        .await
        .context("Failed to close terminal")
        .map_err(|e| e.to_string())?;

    // Broadcast terminal close
    if let Err(e) = app.emit("riterm://terminal-closed", &sessionId) {
        error!("Failed to emit terminal-closed event: {}", e);
    }

    Ok(())
}

#[tauri::command]
async fn rename_terminal(
    sessionId: String,
    newName: Option<String>,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!("Renaming terminal {} to {:?}", sessionId, newName);

    let chat_sender = connections
        .chat_sender
        .lock()
        .unwrap()
        .clone()
        .ok_or_else(|| "Not connected to CLI".to_string())?;

    chat_sender
        .rename_terminal(sessionId, newName)
        .await
        .context("Failed to rename terminal")
        .map_err(|e| e.to_string())?;

    Ok(())
}

#[tauri::command]
async fn send_web_share(
    local_port: u16,
    public_port: u16,
    app: AppHandle,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!("Starting web share: {} -> {}", local_port, public_port);

    let chat_sender = connections
        .chat_sender
        .lock()
        .unwrap()
        .clone()
        .ok_or_else(|| "Not connected to CLI".to_string())?;

    chat_sender
        .send_web_share(local_port, public_port)
        .await
        .context("Failed to start web share")
        .map_err(|e| e.to_string())?;

    // Broadcast web share start
    if let Err(e) = app.emit(
        "riterm://web-share-started",
        &ServerMessage::WebShareStarted {
            local_port,
            public_port,
        },
    ) {
        error!("Failed to emit web-share-started event: {}", e);
    }

    Ok(())
}

#[tauri::command]
async fn send_web_unshare(
    public_port: u16,
    app: AppHandle,
    connections: State<'_, Arc<AppConnections>>,
) -> Result<(), String> {
    info!("Stopping web share: {}", public_port);

    let chat_sender = connections
        .chat_sender
        .lock()
        .unwrap()
        .clone()
        .ok_or_else(|| "Not connected to CLI".to_string())?;

    chat_sender
        .send_web_unshare(public_port)
        .await
        .context("Failed to stop web share")
        .map_err(|e| e.to_string())?;

    // Broadcast web share stop
    if let Err(e) = app.emit(
        "riterm://web-share-stopped",
        &ServerMessage::WebShareStopped { public_port },
    ) {
        error!("Failed to emit web-share-stopped event: {}", e);
    }

    Ok(())
}

#[tauri::command]
async fn get_connection_state(
    state: State<'_, Arc<RitermState>>,
) -> Result<ConnectionState, String> {
    Ok(state.get_connection_state().await)
}

/// Handle gossip events from the iroh network
async fn handle_gossip_event(
    event: shared::Event,
    app: &AppHandle,
    state: &Arc<RitermState>,
    local_node_id: shared::NodeId,
) -> Result<()> {
    // Get event broadcaster if available
    if let Some(broadcaster) = state.get_event_broadcaster().await {
        match event {
            shared::Event::NodeJoined { node_id } => {
                debug!("Node joined: {}", node_id);
                state
                    .update_connection_state(|state| {
                        if !state.peers.contains(&node_id.to_string()) {
                            state.peers.push(node_id.to_string());
                        }
                    })
                    .await?;

                broadcaster
                    .broadcast(ServerMessage::NodeJoined {
                        node_id: node_id.to_string(),
                    })
                    .await;
            }

            shared::Event::NodeLeft { node_id } => {
                debug!("Node left: {}", node_id);
                state
                    .update_connection_state(|state| {
                        state.peers.retain(|peer| peer != &node_id.to_string());
                    })
                    .await?;

                broadcaster
                    .broadcast(ServerMessage::NodeLeft {
                        node_id: node_id.to_string(),
                    })
                    .await;
            }

            shared::Event::MessageReceived {
                from,
                text,
                nickname: _,
            } => {
                debug!("Message received from {}: {}", from.fmt_short(), text);
                // Handle chat messages if needed
            }

            shared::Event::TerminalBatch { from, operations } => {
                debug!("Received terminal batch from {}", from.fmt_short());

                for operation in operations {
                    handle_terminal_operation_with_broadcaster(operation, &broadcaster, state)
                        .await?;
                }
            }

            shared::Event::TerminalState { from, terminals } => {
                debug!("Received terminal state from {}", from.fmt_short());

                let terminal_sessions: Vec<state::TerminalSession> = terminals
                    .clone()
                    .into_iter()
                    .map(state::TerminalSession::from)
                    .collect();

                state
                    .update_connection_state(|state| {
                        state.connected_terminals = terminals;
                    })
                    .await?;

                broadcaster
                    .broadcast(ServerMessage::TerminalStateChanged {
                        terminals: terminal_sessions,
                    })
                    .await;
            }

            shared::Event::WebShare {
                from,
                local_port,
                public_port,
            } => {
                debug!(
                    "Web share request from {}: {} -> {}",
                    from.fmt_short(),
                    local_port,
                    public_port
                );

                let proxy_info = shared::WebShareProxyInfo {
                    local_port,
                    public_port,
                    node_id: from,
                    status: "active".to_string(),
                    created_at: std::time::SystemTime::now()
                        .duration_since(std::time::UNIX_EPOCH)
                        .unwrap()
                        .as_secs(),
                };

                state
                    .update_connection_state(|state| {
                        if !state
                            .active_proxies
                            .iter()
                            .any(|p| p.public_port == public_port)
                        {
                            state.active_proxies.push(proxy_info);
                        }
                    })
                    .await?;

                broadcaster
                    .broadcast(ServerMessage::WebShareStarted {
                        local_port,
                        public_port,
                    })
                    .await;
            }

            shared::Event::WebShareStop { from, public_port } => {
                debug!(
                    "Web share stop request from {}: port {}",
                    from.fmt_short(),
                    public_port
                );

                state
                    .update_connection_state(|state| {
                        state
                            .active_proxies
                            .retain(|p| p.public_port != public_port);
                    })
                    .await?;

                broadcaster
                    .broadcast(ServerMessage::WebShareStopped { public_port })
                    .await;
            }

            shared::Event::WebShareState { from, proxies } => {
                debug!("Received web share state from {}", from.fmt_short());
                state
                    .update_connection_state(|state| {
                        // Only keep proxies from other nodes
                        state.active_proxies = proxies
                            .into_iter()
                            .filter(|p| p.node_id != local_node_id)
                            .collect();
                    })
                    .await?;
            }

            shared::Event::Lagged => {
                warn!("Gossip stream lagged");
            }
        }
    } else {
        // Fallback: direct Tauri events if no broadcaster available
        match event {
            shared::Event::NodeJoined { node_id } => {
                if let Err(e) = app.emit(
                    "riterm://node-joined",
                    &ServerMessage::NodeJoined {
                        node_id: node_id.to_string(),
                    },
                ) {
                    error!("Failed to emit node-joined event: {}", e);
                }
            }
            shared::Event::NodeLeft { node_id } => {
                if let Err(e) = app.emit(
                    "riterm://node-left",
                    &ServerMessage::NodeLeft {
                        node_id: node_id.to_string(),
                    },
                ) {
                    error!("Failed to emit node-left event: {}", e);
                }
            }
            // Handle other events similarly as fallback
            _ => {}
        }
    }

    Ok(())
}

/// Handle individual terminal operations with broadcaster
async fn handle_terminal_operation_with_broadcaster(
    operation: shared::TerminalOp,
    broadcaster: &EventBroadcaster,
    state: &Arc<RitermState>,
) -> Result<()> {
    match operation {
        shared::TerminalOp::Create {
            session_id,
            shell_path: _,
            name: _,
        } => {
            info!("Terminal created: {}", session_id);
            broadcaster
                .broadcast(ServerMessage::TerminalCreated { session_id })
                .await;
        }

        shared::TerminalOp::Output { session_id, data } => {
            broadcaster
                .broadcast(ServerMessage::TerminalOutput { session_id, data })
                .await;
        }

        shared::TerminalOp::Status { session_id, status } => {
            broadcaster
                .broadcast(ServerMessage::TerminalStatus {
                    session_id: session_id.clone(),
                    status: status.clone(),
                })
                .await;

            // Update terminal status in state
            state
                .update_connection_state(|state| {
                    if let Some(terminal) = state
                        .connected_terminals
                        .iter_mut()
                        .find(|t| t.session_id == session_id)
                    {
                        terminal.status = status;
                    }
                })
                .await?;
        }

        shared::TerminalOp::Close { session_id } => {
            info!("Terminal closed: {}", session_id);
            state
                .update_connection_state(|state| {
                    state
                        .connected_terminals
                        .retain(|t| t.session_id != session_id);
                })
                .await?;
        }

        shared::TerminalOp::Rename {
            session_id,
            new_name,
        } => {
            state
                .update_connection_state(|state| {
                    if let Some(terminal) = state
                        .connected_terminals
                        .iter_mut()
                        .find(|t| t.session_id == session_id)
                    {
                        terminal.name = new_name.clone();
                    }
                })
                .await?;
        }

        shared::TerminalOp::Input { .. } | shared::TerminalOp::Resize { .. } => {
            // These operations are handled elsewhere
        }
    }

    Ok(())
}

/// Handle individual terminal operations (legacy fallback)
#[allow(dead_code)]
async fn handle_terminal_operation(
    operation: shared::TerminalOp,
    app: &AppHandle,
    state: &Arc<RitermState>,
) -> Result<()> {
    match operation {
        shared::TerminalOp::Create {
            session_id,
            shell_path: _,
            name: _,
        } => {
            info!("Terminal created: {}", session_id);

            if let Err(e) = app.emit(
                "riterm://terminal-created",
                &ServerMessage::TerminalCreated { session_id },
            ) {
                error!("Failed to emit terminal-created event: {}", e);
            }
        }

        shared::TerminalOp::Output { session_id, data } => {
            if let Err(e) = app.emit(
                &format!("riterm://terminal-output-{}", session_id),
                &ServerMessage::TerminalOutput { session_id, data },
            ) {
                error!("Failed to emit terminal-output event: {}", e);
            }
        }

        shared::TerminalOp::Status { session_id, status } => {
            state
                .update_connection_state(|state| {
                    if let Some(terminal) = state
                        .connected_terminals
                        .iter_mut()
                        .find(|t| t.session_id == session_id)
                    {
                        terminal.status = status.clone();
                    }
                })
                .await?;

            if let Err(e) = app.emit(
                "riterm://terminal-status",
                &ServerMessage::TerminalStatus {
                    session_id: session_id.clone(),
                    status: status.clone(),
                },
            ) {
                error!("Failed to emit terminal-status event: {}", e);
            }
        }

        shared::TerminalOp::Close { session_id } => {
            info!("Terminal closed: {}", session_id);

            state
                .update_connection_state(|state| {
                    state
                        .connected_terminals
                        .retain(|t| t.session_id != session_id);
                })
                .await?;

            if let Err(e) = app.emit("riterm://terminal-closed", &session_id) {
                error!("Failed to emit terminal-closed event: {}", e);
            }
        }

        shared::TerminalOp::Rename {
            session_id,
            new_name,
        } => {
            state
                .update_connection_state(|state| {
                    if let Some(terminal) = state
                        .connected_terminals
                        .iter_mut()
                        .find(|t| t.session_id == session_id)
                    {
                        terminal.name = new_name.clone();
                    }
                })
                .await?;
        }

        shared::TerminalOp::Input { .. } | shared::TerminalOp::Resize { .. } => {
            // These operations are handled elsewhere
        }
    }

    Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    // Initialize logging
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .manage(Arc::new(RitermState::new()))
        .manage(Arc::new(AppConnections::default()))
        .invoke_handler(tauri::generate_handler![
            connect_to_cli,
            disconnect_from_cli,
            create_terminal,
            send_terminal_input,
            resize_terminal,
            close_terminal,
            rename_terminal,
            send_web_share,
            send_web_unshare,
            get_connection_state,
        ])
        .setup(|app| {
            // Handle app shutdown
            let app_handle_clone = app.handle().clone();
            app.listen("tauri://close-requested", move |_| {
                let app_handle_for_async = app_handle_clone.clone();
                tauri::async_runtime::spawn(async move {
                    if let Some(connections) =
                        app_handle_for_async.try_state::<Arc<AppConnections>>()
                    {
                        if let Err(e) = connections.shutdown().await {
                            error!("Error during shutdown: {}", e);
                        }
                    }
                    app_handle_for_async.exit(0);
                });
            });

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
